#include "Test.h"

#include <string>
#include <iostream>
#include <sstream>

std::ostream& operator << (std::ostream& out, const Test test) {
	out << "name:" << test.name << " ignored:" << test.ignored
		<< " file:" << test.file << " line:" << test.line << " runnable:" << test.runnable;
	return out;
}
bool operator == (const Test t1, Test t2) {
	if (   t1.name != t2.name || t1.ignored != t2.ignored
		|| t1.file != t2.file || t1.line != t2.line
		|| t1.runnable != t2.runnable) {
		return false;
	}
	return true;
}

// --- TestLogger ---
TestLoggerImpl::TestLoggerImpl(Logger& injectedLogger)
:	logger(injectedLogger) {
}
void TestLoggerImpl::printSuite(string suiteName) {
	logger.message("\n\n" + suiteName);
}
void TestLoggerImpl::printTest(Test test) {
	printTest(test.name);
	if (test.ignored) {
		printTestState("IGNORED");
	}
	else if (test.assertFailedExceptionOccured) {
		print(test.assertFailedException);
		printTest(test.name);
		printTestState("FAILURE");
	}
	else if (test.unexpectedCallExceptionOccured) {
		print(test.unexpectedCallException, test);
		printTest(test.name);
		printTestState("FAILURE");
	}
	else if (test.unexpectedException) {
		printError("Unexpected Exception!");
		printTest(test.name);
		printTestState("FATAL");
	}
	else { 
		printTestState("OK");
		printDuration(test.durationInMs);
	}
}
void TestLoggerImpl::end() {
	logger.message("\n\n--------------------------------------------------------------");
	logger.message("\n\n");
	/* logger.message("Tests " + intToString(testCount));
	logger.message(" : Valid " + intToString(validCount));
	logger.message(" : Ignores " + intToString(ignoreCount));
	logger.message(" : Warnings " + intToString(warningsCount));
	logger.message(" : Errors " + intToString(failuresCount));
	logger.message("\n##############################################################\n\n"); */
}
void TestLoggerImpl::printTest(string testName) {
	testName.resize(80, ' ');
	logger.message("\n" + testName);
}
void TestLoggerImpl::printError(string error) {
	logger.message("\n  " + error);
}
void TestLoggerImpl::print(AssertFailedException ex) {
	logger.message("\n  AssertFailed -> " + ex.type + ":");
	logger.message("\n  Expected <" + ex.expected + ">");
	logger.message("\n  Actual   <" + ex.actual + ">");
	logger.message("\n  " + ex.file + "(" + intToString(ex.line) + ")");
}
void TestLoggerImpl::print(UnexpectedCallException ex, Test test) {
	logger.message("\n  UnexpectedCallException -> " + ex.methodName);
	logger.message("\n  " + ex.file + "(" + intToString(ex.line) + ")");
	logger.message("\n  " + test.file + "(" + intToString(test.line) + ")");
}
void TestLoggerImpl::printTestState(string testState) {
	testState.resize(7, ' ');
	logger.message("[" + testState + "]");
}
void TestLoggerImpl::printDuration(int durationInMs) {
	stringstream duration;
	duration << " (" << durationInMs <<" ms)";
	logger.message(duration.str());
}

void TestContainer::addTest(string suiteName, Test test) {
	// search the suite
	unordered_map<string, vector<Test> >::iterator testSuiteIterator = tests.find(suiteName);

	// if not exist create new suite
	if (testSuiteIterator == tests.end()) {
		vector<Test> newSuite;
		newSuite.push_back(test);
		tests.insert(pair<string, vector<Test> >(suiteName, newSuite));
	}
	// else add test to suite
	else {
		vector<Test>& suite =  testSuiteIterator->second;
		suite.push_back(test);
	}
}
void TestContainer::addBefore(string suiteName, Runnable* runnable) {
	beforeContainer.insert(pair<string, Runnable*>(suiteName, runnable));
}
void TestContainer::addAfter(string suiteName, Runnable* runnable) {
	afterContainer.insert(pair<string, Runnable*>(suiteName, runnable));
}
vector<string> TestContainer::findSuites() {
	vector<string> suites;
	for (unordered_map<string, vector<Test> >::iterator it = tests.begin(); it != tests.end(); ++it) {
		suites.push_back(it->first);
	}
	return suites;
}
vector<Test> TestContainer::findTestsBySuite(string suiteName) {
	vector<Test> testVector;

	unordered_map<string, vector<Test> >::iterator testSuiteIterator = tests.find(suiteName);

	if (testSuiteIterator == tests.end()) {
		vector<Test> emptyVector;
		return emptyVector;
	}
	// else add test to suite
	else {
		vector<Test>& suite =  testSuiteIterator->second;
		return testSuiteIterator->second;
	}
}
Runnable* TestContainer::findBeforeBySuite(string suiteName) {
	return findRunnableBySuite(suiteName, beforeContainer);
}
Runnable* TestContainer::findAfterBySuite(string suiteName) {
	return findRunnableBySuite(suiteName, afterContainer);
}
Runnable* TestContainer::findRunnableBySuite(string suiteName, unordered_map<string, Runnable*> container) {
	unordered_map<string, Runnable*>::iterator it = container.find(suiteName);
	if (it != container.end()) {
		return it->second;
	}
	return NULL;
}

// --- TestRunner ---
TestRunner::TestRunner(TestLogger& injectedTestLogger)
:	testLogger(injectedTestLogger) {
}
void TestRunner::run(TestContainer testContainer) {
	vector<string> suites = testContainer.findSuites();
	for (unsigned int i=0; i<suites.size(); i++) {
		testLogger.printSuite(suites[i]);

		Runnable* before = testContainer.findBeforeBySuite(suites[i]);
		Runnable* after = testContainer.findAfterBySuite(suites[i]);
		vector<Test> tests = testContainer.findTestsBySuite(suites[i]);

		for (unsigned int j=0; j<tests.size(); j++) {
			try {
				if (before) {
					before->run();
				}
				tests[j].runnable->run();
				if (after) {
					after->run();
				}
			}
			catch (AssertFailedException& ex) {
				tests[j].assertFailedExceptionOccured = true;
				tests[j].assertFailedException = ex;
			}
			catch (UnexpectedCallException& ex) {
				tests[j].unexpectedCallExceptionOccured = true;
				tests[j].unexpectedCallException = ex;
			}
			catch (...) {
				tests[j].unexpectedException = true;
			}
			tests[j].durationInMs = 20;
			testLogger.printTest(tests[j]);
		}
	}
	testLogger.end();
}

// --- TestApp ---
void TestApp::add(string testSuiteName, string testName, Runnable* runnable) {
	Test test;
	test.name = testName;
	test.ignored = false;
	test.runnable = runnable;

	getInstance().testContainer.addTest(testSuiteName, test);
}
void TestApp::addBefore(string testSuiteName, Runnable* runnable) {
	getInstance().testContainer.addBefore(testSuiteName, runnable);
}
void TestApp::addAfter(string testSuiteName, Runnable* runnable) {
	getInstance().testContainer.addAfter(testSuiteName, runnable);
}
void TestApp::run() {
	CoutLogger coutLogger;
	TestLoggerImpl testLogger(coutLogger);
	TestRunner testRunner(testLogger);
	testRunner.run(getInstance().testContainer);
}